package com.heima.article.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.heima.article.dto.ArticleCache;
import com.heima.article.dto.ArticleStreamMessage;
import com.heima.article.entity.ApArticle;
import com.heima.article.service.IApArticleService;
import com.heima.article.service.IComputeService;
import net.sf.jsqlparser.statement.select.KSQLWindow;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

@Service
public class ComputeServiceImpl implements IComputeService {

    @Autowired
    IApArticleService articleService; // 文章信息表，存储已发布的文章

    @Autowired
    StringRedisTemplate redisTemplate; // 缓存到redis


    /**
     * 计算热点文章分值
     */
    @Override
    public void compute() {
        System.out.println("定时计算文章分值任务开始执行:" + new Date());
        //1. 定时任务 每天凌晨1点执行
        //2. 集成xxl-job 1 pom依赖  2 配置类和配置文件  3 添加任务,执行jobHandler 通过@XxlJob指定任务
        //3. 构建查询条件
        LambdaQueryWrapper<ApArticle> query = new LambdaQueryWrapper<>();
        // 查看前5天的文章, 从当天的0点0分0秒 往前推5天
        // 现在时间
        Date now = new Date();
        // 当天的0点0分0秒
        Date end = new Date(now.getYear(), now.getMonth(), now.getDate());
        // 往前推5天
        Date start = new Date(end.getTime() - 5 * 24 * 60 * 60 * 1000);
        // 发布时间小于当前的0点0分0秒
        query.lt(ApArticle::getPublishTime, end);
        // 发布时间小于往前推5天的时间
        query.gt(ApArticle::getPublishTime, start);
        // 过滤已下架的文章
        query.eq(ApArticle::getIsDown, false);
        //过滤已删除的文章
        query.eq(ApArticle::getIsDelete, false);
        //4.  文章信息表，存储已发布的文章, 根据条件查询
        List<ApArticle> list = articleService.list(query);
        //4.1 遍历
        for (ApArticle article : list) {
            // 计算每篇文章的分值 阅读+1, 点赞+3, 评论+5, 收藏+8
            double score = computeScore(article);
            // 缓存文章和分值到redis中  包含推荐首页 和 频道首页
            // 定义推荐首页的key
            String key = "hot_article_0";
            // 定时任务是每天凌晨1点执行一次 计算的是当前0点往前推5天的文章数据
            // 当天0点往后的数据操作也需要将分值计算并且更新到redis中,这些数据通过实时计算来更新
            // 当天操作的数据会更新文章表中的字段 阅读数 点赞数 评论数 收藏数
            // 首页展示列表,不需要文章全部数据,仅需要 文章id 标题 布局 封面图片 作者id 作者名称 发布时间 静态页面地址
            //构建 前端推荐展示的数据 的对象, redis 存到zset里存的member是需要不可变数据
            ArticleCache articleCache = new ArticleCache();
            // copy数据, 从文章信息表co到 前端展示数据的对象中
            BeanUtils.copyProperties(article, articleCache);
            // 定义value  文章的json数据
            String value = JSON.toJSONString(articleCache);
            //4.2 存到redis缓存数据库中,调用redis 进行保存
            redisTemplate.opsForZSet().add(key, value, score);
            //4.3 定义频道首页的key, 频道首页的缓存 , 获取文章所属频道ID
            String channelKey = "hot_article_" + article.getChannelId();
            redisTemplate.opsForZSet().add(channelKey, value, score);
            //4.4 设置过期时间
            redisTemplate.expire(key, 23 * 60 * 58, TimeUnit.MINUTES);
            redisTemplate.expire(channelKey, 23 * 60 * 58, TimeUnit.MINUTES);
        }
    }

    /**
     * 缓存和数据库的更新
     *
     * @param message
     */
    @Override
    public void update(ArticleStreamMessage message) {
        //1. 根据文章id查询文章
        ApArticle article = articleService.getById(message.getArticleId());
        //2. 一段时间内的聚合结果, 计算本次聚合的分值
        double scorePlus = computeScore(message);
        //3. 更新redis中的分值, 判断文章是否已经存在redis中
        //3.1 定义key 拿到文章所属频道ID
        String key = "hot_article_" + article.getChannelId();
        //3.2 构建 接收前端推荐展示的数据 的对象
        ArticleCache articleCache = new ArticleCache();
        //3.3 从文章作者copy数据到 前端展示的对象中去
        BeanUtils.copyProperties(article, articleCache);
        //4. 定义value
        String value = JSON.toJSONString(articleCache);
        //5. 调redis进行保存
        Double score = redisTemplate.opsForZSet().score(key, value);
        //6. 对分数非空判断
        if (score == null){
            // 如果不存在, 先计算历史分值, 再加上增量分值, 写入redis中
            double hisScore = computeScore(article);
            // 历史分数 + 增量分数
            double totalScore = hisScore + scorePlus;
            // redis进行添加
            redisTemplate.opsForZSet().add("hot_article_0", value, totalScore); // 推荐首页
            redisTemplate.opsForZSet().add(key, value, totalScore); // 频道首页
            // 添加过期时间
            // 频道首页的过期时间
            redisTemplate.expire(key, 23 * 60 * 58, TimeUnit.MINUTES);
            // 推荐首页的过期时间
            redisTemplate.expire("hot_article_0", 23 * 60 * 58, TimeUnit.MINUTES);
            System.out.println("在redis中新增文章数据: 文章内容: " + value + " ,总分值:  " + totalScore);
        } else {
            // 如果存在的话, 直接将增量的分值加到原有的分值上
            redisTemplate.opsForZSet().incrementScore(key, value, scorePlus);//频道首页的
            redisTemplate.opsForZSet().incrementScore("hot_article_0", value, scorePlus);//推荐首页的
            System.out.println("文章已经存在与redis中,添加增量分值: " + scorePlus);
        }
        //7. 更新文章表的数据, 构建更新条件
        LambdaUpdateWrapper<ApArticle> update = new LambdaUpdateWrapper<>();
        // 文章作者id 条件
        update.eq(ApArticle::getId, message.getArticleId());
        // sql条件
        update.setSql("views = views + " + message.getView()); // 阅读
        update.setSql("likes = likes + " + message.getLike()); // 喜欢
        update.setSql("comment = comment + " + message.getComment()); // 评论
        update.setSql("collection = collection + " + message.getCollect()); // 收藏
        // 调用文章信息表更新数据库
        articleService.update(update);
        System.out.println("更新文章表数据: 阅读量: " + message.getView()
                + ",点赞量: "  + message.getLike()
                + ",评论量: " + message.getComment()
                + " ,收藏量: " + message.getCollect());
    }

    /**
     * 计算当日的增量分值
     *
     * @param message
     * @return
     */
    private double computeScore(ArticleStreamMessage message) {
        //0. 定义分值初始值
        double score = 0;
        //1. 阅读数量 * 1 *3
        score += message.getView() * 1 * 3;
        //2. 点赞数量 * 3 *3
        score += message.getLike() * 3 * 3;
        //3. 评论数量 * 5 *3
        score += message.getComment() * 5 * 3;
        //4. 收藏数量 * 8 *3
        score += message.getCollect() * 8 * 3;
        //5. 返回分数
        return score;
    }

    /**
     * 计算文章的分值
     *
     * @param article
     * @return
     */
    private double computeScore(ApArticle article) {
        //0. 定义分值初始值
        int score = 0;
        //1. 阅读数量 + 1
        if (article.getViews() != null) {
            score += article.getViews() * 1;
        }
        //2. 点赞数量 + 3
        if (article.getLikes() != null) {
            score += article.getLikes() * 3;
        }
        //3. 评论数量 + 5
        if (article.getComment() != null) {
            score += article.getComment() * 5;
        }
        //4. 收藏数量 + 8
        if (article.getCollection() != null) {
            score += article.getCollection() * 8;
        }
        //5. 返回分数
        return score;
    }
}
